package net.mgsx.product.t2m.modisco.impact;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.mgsx.product.t2m.modisco.Activator;
import net.mgsx.product.t2m.modisco.impact.analyser.FileImpactAnalyser;
import net.mgsx.product.t2m.modisco.impact.analyser.ImpactAnalyser;
import net.mgsx.product.t2m.modisco.tools.ModiscoUtils;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmt.modisco.infra.common.core.logging.MoDiscoLogger;
import org.eclipse.gmt.modisco.java.ASTNode;
import org.eclipse.gmt.modisco.java.AbstractMethodDeclaration;
import org.eclipse.gmt.modisco.java.AbstractMethodInvocation;
import org.eclipse.gmt.modisco.java.CompilationUnit;
import org.eclipse.gmt.modisco.java.Model;
import org.eclipse.gmt.modisco.java.NamedElement;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;

public class AnalysisStorage {

	private static AnalysisStorage instance;

	/**
	 * @return the instance
	 */
	protected static final AnalysisStorage getInstance() {
		if(instance == null){
			instance = new AnalysisStorage();
		}
		return instance;
	}
	
	private Map<ASTNode, AnalysisInformation> informations;
	
	private List<ASTNode> analysedNodes;
	private List<ASTNode> modifiedNodes;
	private boolean validated;
	
	public AnalysisStorage() {
		init();
	}
	
	private void init(){
		informations = new HashMap<ASTNode, AnalysisInformation>();
		analysedNodes = new ArrayList<ASTNode>();
		modifiedNodes = new ArrayList<ASTNode>();
		validated = true;
	}
	
	public boolean commitAnalysis(){
		final boolean result;
		if(!validated){
			validate();
			validated = true;
			result = true;
		}else{
			result = false;
		}
		return result;
	}
	
	private static AnalysisConsolidation createConsolidation(Map<ASTNode, AnalysisInformation> infos){
		int nbImpacted = 0;
		int nbTotal = 0;
		int insImpacted = 0;
		int insTotal = 0;
		for(ASTNode node : infos.keySet()){
			AnalysisInformation info = infos.get(node);
			if(info.impacted){
				insImpacted += info.nbInstructions;
				nbImpacted++;
			}
			insTotal += info.nbInstructions;
			nbTotal++;
		}
		AnalysisConsolidation conso = new AnalysisConsolidation();
		conso.globalComplexity = insTotal;
		conso.impactedComplexity = insImpacted;
		conso.totalElements = nbTotal;
		conso.impactedElements = nbImpacted;
		conso.impactRate = (double)nbImpacted / (double)nbTotal;
		conso.complexityRate = (double)insImpacted / (double)insTotal;
		return conso;
	}
	
	private void validate(){
		// TODO
		// 1) mark all modified as visited
		// not necessary
		
		// 2) remove redudant children (hierarchy)
		// nothing to do
		
		// 3) start global analysis
		List<Model> models = new ArrayList<Model>();
		for(ASTNode node : modifiedNodes){
			if(node instanceof AbstractMethodDeclaration){
				Model model = getModel(node);
				if(!models.contains(model)){
					models.add(model);
					scanModelRecursive(model);
				}
			}
		}
		
		// 4) start modif analysis
		for(ASTNode node : modifiedNodes){
			if(node instanceof AbstractMethodDeclaration){
				scanMethodRecursive((AbstractMethodDeclaration)node);
			}
		}
		
		// 5) consolidate
		int nbUsed = 0;
		int nbTotal = 0;
		for(ASTNode md : informations.keySet()){
			AnalysisInformation info = informations.get(md);
			if(analysedNodes.contains(md)){
				nbUsed += info.nbInstructions;
			}
			nbTotal += info.nbInstructions;
		}
		double value = (double)nbUsed / (double)nbTotal;
		int nbElements = modifiedNodes.size();
		
		// file analysis / consolidation
		final ImpactAnalyser fileAnalyser = new FileImpactAnalyser();
		Map<ASTNode, AnalysisInformation> filesAnalysis = 
			fileAnalyser.analyse(modifiedNodes);
		final AnalysisConsolidation filesConsolidation = createConsolidation(filesAnalysis);
		
		// 6) print out result
		File file = new File("t2m.log"); // TODO parameter ...
		FileWriter writer;
		try {
			writer = new FileWriter(file, false);
			writer.write("modified elements : " + nbElements + "\n");
			writer.write("instructions : " + nbUsed + " / " + nbTotal + "\n");
			writer.write("complexity : " + (value * 100) + " %\n");
			
			writer.write("files impacted : " + filesConsolidation.impactRate + " (" + filesConsolidation.impactedElements + " / " + filesConsolidation.totalElements + ")\n");
			writer.write("files impact : " + filesConsolidation.complexityRate + " (" + filesConsolidation.impactedComplexity + " / " + filesConsolidation.globalComplexity + ")\n");
			writer.write("impacted files :\n");
			for(ASTNode node : filesAnalysis.keySet()){
				if(filesAnalysis.get(node).impacted){
					writer.write(((CompilationUnit)node).getOriginalFilePath() + "\n");
				}
			}
			
			writer.write("impacted elements :\n");
			for(ASTNode node : analysedNodes){
				writer.write((node instanceof NamedElement ? ((NamedElement)node).getName() : node.eClass().getName())
						+ " : " + (node.getOriginalCompilationUnit() != null ? node.getOriginalCompilationUnit().getOriginalFilePath() : "null") + "\n");
			}
			
			
			writer.close();
			
			IWebBrowser browser = PlatformUI.getWorkbench().getBrowserSupport().createBrowser(null);
			browser.openURL(file.toURI().toURL());
			
			
		} catch (IOException e) {
			MoDiscoLogger.logError(e, Activator.getDefault());
		} catch (PartInitException e) {
			MoDiscoLogger.logError(e, Activator.getDefault());
		}
		init();
	}
	
	public boolean willBeAnalysed(ASTNode node){
		return true; // node instanceof AbstractMethodDeclaration;
	}
	public void addAsModifiedElement(ASTNode node){
		validated = false;
		if(/*node instanceof AbstractMethodDeclaration && */!modifiedNodes.contains(node)){
			modifiedNodes.add(node);
		}
	}
	
	private void scanModelRecursive(Model model)
	{
		for(AbstractMethodDeclaration md : ModiscoUtils.getElements(model, AbstractMethodDeclaration.class))
		{
			AnalysisInformation info = new AnalysisInformation();
			info.nbInstructions = getNbInstructions(md);
			informations.put(md, info);
		}
		
	}
	
	private int getNbInstructions(AbstractMethodDeclaration md){
		// TODO voir pour ASTNode ou STATEMENT
		return ModiscoUtils.getElements(md, ASTNode.class).size();
	}
	
	private void scanMethodRecursive(AbstractMethodDeclaration context){
		if(context != null)
		{
			// mark method as visited
			analysedNodes.add(context);
			for (AbstractMethodInvocation invocation : context.getUsages()) {
				AbstractMethodDeclaration caller = (AbstractMethodDeclaration)ModiscoUtils.getOwner(invocation, AbstractMethodDeclaration.class);
				if(caller == null){
					MoDiscoLogger.logWarning(ModiscoUtils.formatMessage("invocation is not in a method declaration", invocation), Activator.getDefault());
				}else if(!analysedNodes.contains(caller)){
					scanMethodRecursive(caller);
				}
			}
		}
	}
	
	private Model getModel(EObject object){
		return (Model)ModiscoUtils.getOwner(object, Model.class);
	}
	
}
